//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/image.h>

#include <buola/image/ctextlayout.h>
#include <buola/image/cgc.h>

#include <unicode/ubrk.h>

#include <buola/image/clayoutengine.h>

namespace buola { namespace img {

CTextLayout::CTextLayout()
    :   mFlags(FLAG_NONE)
    ,   mWrapMode(WRAP_WORD)
    ,   mEllipsizeMode(ELLIPSIZE_NONE)
    ,   mAlignMode(ALIGN_LEFT)
    ,   mBiDi(nullptr)
{
}

CTextLayout::CTextLayout(const std::wstring &pText)
    :   mFlags(FLAG_NONE),
        mText(pText),
        mUText(utf16(pText)),
        mWrapMode(WRAP_WORD),
        mEllipsizeMode(ELLIPSIZE_NONE),
        mAlignMode(ALIGN_LEFT),
        mBiDi(nullptr)
{
}

CTextLayout::~CTextLayout()
{
    if(mBiDi)
        ubidi_close(mBiDi);
}

void CTextLayout::SetText(const std::wstring &pText)
{
    mText=pText;
    mUText=utf16(pText);
    
    mFlags&=~VALID_ALL;
}

void CTextLayout::SetDefaultFont(PFont pFont)
{
    mDefaultFont=pFont;
    
    mFlags&=~VALID_ALL;
}

void CTextLayout::SetWidth(double pW)
{
    mWrapSize.x=pW;
    mFlags|=FLAG_WIDTH;
    mFlags&=~FLAG_WIDTHCOLS;
    
    mFlags&=~VALID_LAYOUT;
}

void CTextLayout::SetHeight(double pH)
{
    mWrapSize.y=pH;
    mFlags|=FLAG_HEIGHT;
    mFlags&=~FLAG_HEIGHTLINES;
    
    mFlags&=~VALID_LAYOUT;
}

void CTextLayout::SetWidthCols(int pW)
{
    mWrapSize.x=pW;
    mFlags|=FLAG_WIDTHCOLS;
    mFlags&=~FLAG_WIDTH;
    
    mFlags&=~VALID_LAYOUT;
}

void CTextLayout::SetHeightLines(int pH)
{
    mWrapSize.y=pH;
    mFlags|=FLAG_HEIGHTLINES;
    mFlags&=~FLAG_HEIGHT;
    
    mFlags&=~VALID_LAYOUT;
}

void CTextLayout::UnsetWidth()
{
    mFlags&=~(FLAG_WIDTH|FLAG_WIDTHCOLS);

    mFlags&=~VALID_LAYOUT;
}

void CTextLayout::UnsetHeight()
{
    mFlags&=~(FLAG_HEIGHT|FLAG_HEIGHTLINES);

    mFlags&=~VALID_LAYOUT;
}

void CTextLayout::SetWrapMode(EWrapMode pMode)
{
    mWrapMode=pMode;

    mFlags&=~VALID_LAYOUT;
}

void CTextLayout::SetEllipsizeMode(EEllipsizeMode pMode)
{
    mEllipsizeMode=pMode;

    mFlags&=~VALID_LAYOUT;
}

void CTextLayout::SetAlignMode(EAlignMode pMode)
{
    mAlignMode=pMode;

    mFlags&=~VALID_LAYOUT;
}

const CSize_d &CTextLayout::GetSize()
{
    if(!(mFlags&VALID_LAYOUT)) CalcLayout();
    
    return mSize;
}

CPoint_d CTextLayout::GetPositionOffset(ETextPos pRef)
{
    CPoint_d lPoint(0,0);

    if(pRef&ETextPos::RIGHT)
    {
        lPoint.x-=mSize.x;
    }
    else if(pRef&ETextPos::HCENTER)
    {
        lPoint.x-=round(mSize.x/2.0);
    }
        
    if(pRef&ETextPos::BOTTOM)
    {
        lPoint.y-=mSize.y;
    }
    else if(pRef&ETextPos::BASELINE)
    {
        lPoint.y-=mDefaultFont->Metrics().mAscent;
    }
    else if(pRef&ETextPos::VCENTER)
    {
        lPoint.y-=round(mSize.y/2.0);
    }

    return lPoint;
}

void CTextLayout::Draw(const CPoint_d &pPos,CGC *pGC,ETextPos pRef)
{
    if(!(mFlags&VALID_LAYOUT)) CalcLayout();

    if(!mDefaultFont)
    {
        throw XInvalid("drawing layout without a font");
    }
    
    CPoint_d lPoint=pPos+GetPositionOffset(pRef);

    for(int i=0,n=0;i<mVisualRun.count();i++)
    {
        pGC->DrawGlyphs(mDefaultFont,mVisualRun.GetValue(i).mFont,
                &mGlyphIndices[n],mVisualRun.GetLimit(i)-n,
                mGlyphs,lPoint);
        n=mVisualRun.GetLimit(i);
    }
}

void CTextLayout::FillBack(const SCursor &pFrom,const SCursor &pTo,const CPoint_d &pPos,CGC *pGC,ETextPos /*pRef*/)
{
    int lFromLine=mLinesRun.GetIndex(pFrom.AbsIndex());
    int lToLine=mLinesRun.GetIndex(pTo.AbsIndex()-1);

    int lC=lFromLine>0?mLinesRun.GetLimit(lFromLine-1):0;
    
    for(int l=lFromLine;l<=lToLine;l++)
    {
        int lLimit=mLinesRun.GetLimit(l);

        int lFromChar=std::max(lC,pFrom.AbsIndex());
        int lToChar=std::min(lLimit,pTo.AbsIndex())-1;

        CRect_d lStart=GetCursorPos(SCursor(lFromChar,false));
        CRect_d lEnd=GetCursorPos(SCursor(lToChar,true));

        pGC->Path().Rect(CPoint_d(lStart.l,lStart.t)+pPos,CSize_d(lEnd.l-lStart.l,lStart.h()));

        lC=mLinesRun.GetLimit(l);
    }

    pGC->FillPath();
}

CPoint_d CTextLayout::GetPos(int pN)
{
    if(!(mFlags&VALID_LAYOUT)) CalcLayout();
    
    int lGlyph=mCharToGlyphMap[pN];
    
    if(lGlyph>=mGlyphs.size())
    {
        const SGlyph &lGlyph=mGlyphs.back();
        return CPoint_d(lGlyph.mPos.x+lGlyph.mAdvance,lGlyph.mPos.y);
    }
    else
    {
        return mGlyphs[lGlyph].mPos;
    }
}

CRect_d CTextLayout::GetCursorPos(const SCursor &pCursor)
{
    if(!(mFlags&VALID_LAYOUT)) CalcLayout();
    
    if(!mGlyphs.size())
    {
        const CFontMetrics &lMetrics=mDefaultFont->Metrics();
        return CRect_d(0,0,0,lMetrics.mHeight);
    }

    const SLine& lLine=mLinesRun.GetValue(
            min(mLinesRun.GetIndex(pCursor.mIndex),mLinesRun.count()-1));

    int lGlyphIndex=mCharToGlyphMap[pCursor.mIndex];

    if(lGlyphIndex>=mGlyphs.size())
    {
        const SGlyph &lGlyph=mGlyphs.back();

        return CRect_d(lGlyph.mPos.x+lGlyph.mAdvance,lLine.mTop,
                lGlyph.mPos.x+lGlyph.mAdvance,lLine.mBottom);
    }
    else
    {
        const SGlyph &lGlyph=mGlyphs[lGlyphIndex];

        if(!pCursor.mTrailing)
        {
            return CRect_d(lGlyph.mPos.x,lLine.mTop,
                    lGlyph.mPos.x,lLine.mBottom);
        }
        else
        {
            return CRect_d(lGlyph.mPos.x+lGlyph.mAdvance,lLine.mTop,
                    lGlyph.mPos.x+lGlyph.mAdvance,lLine.mBottom);
        }
    }
}

int CTextLayout::GetIndex(const CPoint_d &pPos)
{
    if(!(mFlags&VALID_LAYOUT)) CalcLayout();
    
    for(int i=0;i<mGlyphIndices.size();i++)
    {
        const SGlyph &lGlyph=mGlyphs[mGlyphIndices[i]];

        if(lGlyph.mPos.x+lGlyph.mAdvance<pPos.x) continue;
        
        for(int j=0;j<mText.size();j++)
        {
            if(mCharToGlyphMap[j]<i) continue;
            return j;
        }
        break;
    }
    
    return mText.size();
}

CTextLayout::SCursor CTextLayout::GetCursor(const CPoint_d &pPos)
{
    if(!(mFlags&VALID_LAYOUT)) CalcLayout();
    
    for(int i=0;i<mGlyphIndices.size();i++)
    {
        const SGlyph &lGlyph=mGlyphs[mGlyphIndices[i]];

        if(lGlyph.mPos.x+lGlyph.mAdvance/2<pPos.x) continue;
        
        SCursor lRet;
        
        lRet.mTrailing=false;
        
        for(int j=0;j<mText.size();j++)
        {
            if(mCharToGlyphMap[j]<i) continue;
            lRet.mIndex=j;
            return lRet;
        }

        break;
    }
    
    SCursor lRet;
        
    lRet.mIndex=mText.size()-1;
    lRet.mTrailing=true;

    return lRet;
}

void CTextLayout::MoveCursor(SCursor &pCursor,ECursorMove pMove)
{
    if(pMove==CURSOR_LEFT)
    {
        if(pCursor.mTrailing)
        {
            pCursor.mTrailing=false;
        }
        else
        {
            if(pCursor.mIndex>0)
                pCursor.mIndex--;
        }
    }
    else if(pMove==CURSOR_RIGHT)
    {
        if(!pCursor.mTrailing)
        {
            pCursor.mTrailing=true;
        }
        else
        {
            if(pCursor.mIndex<mText.size()-1)
                pCursor.mIndex++;
        }
    }
    else if(pMove==CURSOR_HOME)
    {
        pCursor.mIndex=0;
        pCursor.mTrailing=false;
    }
    else if(pMove==CURSOR_END)
    {
        pCursor.mIndex=mText.size()-1;
        pCursor.mTrailing=true;
    }
}

void CTextLayout::InsertText(SCursor &pCursor,const std::wstring &pString)
{
    mText.insert(pCursor.AbsIndex(),pString);

    pCursor.mIndex+=pString.length();

    mUText=utf16(mText);
    mFlags&=~VALID_ALL;
}

void CTextLayout::InsertChar(SCursor &pCursor,wchar_t pChar)
{
    mText.insert(pCursor.AbsIndex(),1,pChar);

    pCursor.mIndex++;

    mUText=utf16(mText);
    mFlags&=~VALID_ALL;
}

void CTextLayout::EraseChar(SCursor &pCursor,bool pBack)
{
    if(pBack)
    {
        if(pCursor.mTrailing)
        {
            pCursor.mTrailing=false;
        }
        else
        {
            if(pCursor.mIndex==0) return;
            pCursor.mIndex--;
        }
    }
    else
    {
        if(pCursor.AbsIndex()>=mText.length()) return;
    }
    
    mText.erase(pCursor.AbsIndex(),1);

    mUText=utf16(mText);
    mFlags&=~VALID_ALL;
}

static CTextLayout::SAnalysis AnalysisCombiner(UBiDiLevel pLevel,
            UScriptCode pScript,TFontIndex pFont)
{
    CTextLayout::SAnalysis lA;
    lA.mLevel=pLevel;
    lA.mScript=pScript;
    lA.mRTL=pLevel&1;
    lA.mFont=pFont;

    return lA;
}

void CTextLayout::CalcAnalysis()
{
    UErrorCode lCode=U_ZERO_ERROR;
    
    //calculate bidirectional runs
    mBidiRun.clear();

    if(mBiDi)
        ubidi_close(mBiDi);
        
    mBiDi=ubidi_openSized(mUText.size(),0,&lCode);
    ubidi_setPara(mBiDi,mUText.c_str(),mUText.size(),UBIDI_DEFAULT_LTR,
                      nullptr,&lCode);
    int lCount=ubidi_countRuns(mBiDi,&lCode);

    for(int i=0,n=0;i<lCount;i++)
    {
        UBiDiLevel lLevel;
        int lLimit;

        ubidi_getLogicalRun(mBiDi,n,&lLimit,&lLevel);
        mBidiRun.Add(lLimit,lLevel);

        n=lLimit;
    }

    //calculate script runs
    mScriptsRun.clear();

    UScriptCode lCurrent=USCRIPT_COMMON;
    
    for(int i=0;i<mText.size();i++)
    {
        UScriptCode lScript=uscript_getScript(mText[i],&lCode);

        if(lScript<=USCRIPT_INHERITED) continue;
        
        if(lCurrent<=USCRIPT_INHERITED)
        {
            lCurrent=lScript;
        }
        else if(lScript!=lCurrent)
        {
            mScriptsRun.Add(i,lCurrent);
            lCurrent=lScript;
        }
    }

    mScriptsRun.Add(mText.size(),lCurrent);

    mFlags|=VALID_ANALYSIS;
}

void CTextLayout::CalcGlyphs()
{
    if(!(mFlags&VALID_ANALYSIS)) CalcAnalysis();
    
    if(!mDefaultFont)
        throw XInvalid("calculating glyphs without a font");
    
    //after the analysis, and before actually calculating the glyphs,
    //we need to find the subfont for each character
    //there are three ways to do that:
    //-find the first font in the set for each character, possibly skipping
    //spaces (as long as the last selected font can represent them)
    //-once we have found a font for a character, keep that font until there
    //is a character it can't represent, then for next character, 
    //check again from first font in the set
    //-for each group of characters with same analysis, check the first font
    //which can represent the first character. if we find a character that
    //font can't represent, check if there is a font that can represent that
    //character and all the previous ones. then continue until we find a
    //character the new font can't represent. continue until the group is over
    //or there is no font which can represent all the characters

    //calculate subfont runs
    mSubFontRun.clear();
    
    TFontIndex lCurrent=-1;
    
    for(int i=0;i<mText.size();i++)
    {
        if(u_iscntrl(mText[i])||mText[i]==' ')
            continue;
        
        TFontIndex lF=mDefaultFont->GetSubFont(mText[i]);
        
        if(lCurrent==-1)
        {
            lCurrent=lF;
        }
        else if(lF!=lCurrent)
        {
            mSubFontRun.Add(i,lCurrent);
            lCurrent=lF;
        }
    }

    if(lCurrent==-1) lCurrent=0;

    mSubFontRun.Add(mText.size(),lCurrent);
    
    //combine runs into analysis run, which also has rtl direction    
    combine_runs(mAnalysisRun,mBidiRun,mScriptsRun,mSubFontRun,AnalysisCombiner);

    mGlyphs.clear();
    mCharToGlyphMap.clear();
    mCharToGlyphMap.reserve(mUText.size()+1);
    
    for(int i=0,lC=0;i<mAnalysisRun.count();i++)
    {
        int lLimit=mAnalysisRun.GetLimit(i);

        SAnalysis &lA=mAnalysisRun.GetValue(i);
        
        CLayoutEngine *lE=mDefaultFont->GetEngine(lA);

        lE->LayoutChars(mUText,lC,lLimit,mGlyphs,mCharToGlyphMap,lA.mRTL);

        lC=lLimit;
    }
    
    mCharToGlyphMap.push_back(mGlyphs.size());
    
    mFlags|=VALID_GLYPHS;
}

void CTextLayout::CalcLines()
{
    mLinesRun.clear();

    if(!mUText.size()) return;

    if(!(mFlags&(FLAG_WIDTH|FLAG_WIDTHCOLS)))
    {
        mLinesRun.Add(mUText.size(),SLine());
        return;
    }
    
    UErrorCode lCode=U_ZERO_ERROR;
    UBreakIterator *lBrkIt=ubrk_open(UBRK_LINE,nullptr,mUText.c_str(),
                              mUText.length(),&lCode);

    int lC=0;

    while(lC<mUText.size())
    {
        int lEnd;

        if(mFlags&FLAG_WIDTHCOLS)
        {
            int lW=(int)mWrapSize.x;
            for(lEnd=lC;lW&&lEnd<mUText.size();lEnd++)
            {
                while(mCharToGlyphMap[lEnd]==mCharToGlyphMap[lEnd+1]) lEnd++;
                lW--;
            }
        }
        else
        {
            double lW=0;
            lEnd=lC;
            
            while(lEnd<mUText.size())
            {
                int lNextCluster=lEnd+1;
                
                while(mCharToGlyphMap[lNextCluster]==mCharToGlyphMap[lEnd])
                    lNextCluster++;
                    
                for(int i=mCharToGlyphMap[lEnd];i<mCharToGlyphMap[lNextCluster];i++)
                    lW+=mGlyphs[i].mAdvance;
                
                if(lW>mWrapSize.x) break;

                lEnd=lNextCluster;
            }
        }
        
        while(lEnd<mUText.size()&&u_isUWhiteSpace(mUText[lEnd]))
            lEnd++;
        
        if(lEnd<mUText.size())
        {
            int lPrev=ubrk_preceding(lBrkIt,lEnd+1);
        
            //if we can't fit a whole word, fit as many characters as we can

            if(lPrev>lC) 
            {
                lEnd=lPrev;
            }
        
            //don't cut in the middle of a cluster
            while(lEnd>lC&&mCharToGlyphMap[lEnd]==mCharToGlyphMap[lEnd-1]) lEnd--;

            if(lEnd==lC)
            {
                //advance one cluster
                while(mCharToGlyphMap[lEnd]==mCharToGlyphMap[lC])
                    lEnd++;
            }
        }
            
        mLinesRun.Add(lEnd,SLine());
        lC=lEnd;
    }

    ubrk_close(lBrkIt);
}

void CTextLayout::CalcLayout()
{
    if(!(mFlags&VALID_GLYPHS)) 
        CalcGlyphs();
    
    const CFontMetrics &lMetrics=mDefaultFont->Metrics();

    CPoint_d lPoint(0,lMetrics.mAscent);
    
    mVisualRun.clear();
    mGlyphIndices.clear();

    mSize={0,0};
    
    CalcLines();

    for(int l=0,lC=0;l<mLinesRun.count();l++)
    {
        int lFirstGlyph=mGlyphIndices.size();
        int lLimit=mLinesRun.GetLimit(l);
        
        //sort glyphs
        for(int i=mAnalysisRun.GetIndex(lC),n=lC;
                        i<mAnalysisRun.count()&&n<lLimit;i++)
        {
            if(!mAnalysisRun.GetValue(i).mRTL)
            {
                for(int j=n;j<mAnalysisRun.GetLimit(i)&&j<lLimit;j++)
                {
                    for(int k=mCharToGlyphMap[j];k<mCharToGlyphMap[j+1];k++)
                        mGlyphIndices.push_back(k);
                }
            }
            else
            {
                for(int j=std::min(mAnalysisRun.GetLimit(i),lLimit)-1;j>=n;j--)
                {
                    for(int k=mCharToGlyphMap[j+1]-1;k>=mCharToGlyphMap[j];k--)
                        mGlyphIndices.push_back(k);
                }
            }

            n=mAnalysisRun.GetLimit(i);
        }

        lPoint.x=0;

        //calculate visual runs
        int lCurFontIndex=-1;

        for(int i=lFirstGlyph;i<mGlyphIndices.size();i++)
        {
            SGlyph &lGlyph=mGlyphs[mGlyphIndices[i]];

            lGlyph.mPos=lPoint;
            lGlyph.mPos+=lGlyph.mOffset;
            lPoint.x+=lGlyph.mAdvance;

            if(lCurFontIndex<0)
            {
                lCurFontIndex=lGlyph.mFont;
            }
            else if(lCurFontIndex!=lGlyph.mFont)
            {
                SVisual lVisual;
                lVisual.mFont=lCurFontIndex;
                mVisualRun.Add(i,lVisual);
                lCurFontIndex=lGlyph.mFont;
            }
        }

        SVisual lVisual;
        lVisual.mFont=lCurFontIndex;
        mVisualRun.Add(mGlyphIndices.size(),lVisual);

        if(mFlags&FLAG_WIDTH)
        {
            if(mAlignMode==ALIGN_CENTER||mAlignMode==ALIGN_RIGHT)
            {
                double lInc=mWrapSize.x-lPoint.x;
                
                if(mAlignMode==ALIGN_CENTER)
                    lInc/=2.0;
                    
                for(int i=lFirstGlyph;i<mGlyphIndices.size();i++)
                {
                    mGlyphs[mGlyphIndices[i]].mPos.x+=lInc;
                }
            }
        }
  //      else
        {
            mSize.x=std::max(mSize.x,lPoint.x);
        }
 
        mLinesRun.GetValue(l).mTop=lPoint.y-lMetrics.mAscent;
        mLinesRun.GetValue(l).mBottom=lPoint.y-lMetrics.mAscent+lMetrics.mHeight;

        lPoint.y+=lMetrics.mHeight;
        mSize.y+=lMetrics.mHeight;

        lC=lLimit;
    }
    
    if(mSize.y<lMetrics.mHeight)
        mSize.y=lMetrics.mHeight;

    mFlags|=VALID_LAYOUT;
}

/*namespace img*/ } /*namespace buola*/ }
